#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/time.h>


#define ADDRESS "224.0.50.112"
#define SERVICE "5050"
#define MAX_BUF_SIZE 65535
#define TIME-X 25
#define TIME-Y 100
#define PORT 5050
#define ACCTID 417130

{

while(true)
	remainingTime = nextSendTime - currTime
	reset x
	if(remaining time <= 0 OR (x = select(timeout = remainingTime)) == 0)
		sendMessage()
		nextSendTime += 25 seconds
		continue
	else if(x < 0)
		error
	else if(x > 0)
		if(read socket ready)
			readAndInterpret()
		if(read stdin ready)
			if(readAndExecute())
				break

			
}

//CHANGE TIME NOTATION TO TIMEVAL



typedef struct _neighborNode{
	char ip[20];
	int port;
	int netAddr;
	int accountID;
	int isAlive;
	int lastMsgTime;
	_neighborNode *next;
} neighborNode;

typedef struct _header{
	uint8_t version; /* must be 1. If you receive anything else, discard*/
	uint8_t ttl; /* must be 1. If you receive anything else, discard*/
	uint16_t payload_length; /* bytes following the header */
	uint32_t account_identifier; /* digits of your account name */
	uint32_t source_address; /* unused for now, set to 0 and ignore. */
	uint32_t destination_address; /* unused for now, set to 0 and ignore. */
	uint16_t checksum; /* unused for now, set to 0 and ignore. */
	uint16_t protocol; /* must be 1. If you receive anything else, discard*/
} header;



int my_error(char *message){
	fprintf(stderr, "error: %s\n", message);
	exit(1);
}


int make_header_str(char *dest, int pay_len){
	header h;
	int source;
	
	if(pay_len > MAX_BUF_SIZE - 28){
		my_error("message too long");
	}
	
	
	source = (getpid() << 16) + ((ACCTID % 100) << 8) + 42;
	
	
	h.version = 1;
	h.ttl = 1;
	h.payload_length = htons(pay_len);
	h.account_identifier = htonl(ACCTID);
	h.source_address = htonl(source);
	h.destination_address = htonl(0);
	h.checksum = htons(0);
	h.protocol = htons(1);
	
	memcpy(dest, &h, sizeof(h));
	
	
	return 1;
}

/*
*Return value: payload length
*/
int check_header_str(char *src){
	header h;
	
	memcpy(&h, src, sizeof(h));
	
	if(h.version != 1){
		printf("version = %d error\n", h.version);
		return 0;
	}
	if(h.ttl != 1){
		printf("ttl = %d error\n", h.ttl);
		return 0;
	}
	if(ntohs(h.payload_length) > (MAX_BUF_SIZE - 28)){
		printf("payload_length = %d error\n", ntohs(h.payload_length));
		return 0;
	}
	if(ntohs(h.protocol) != 1){
		printf("protocol = %d error\n", ntohs(h.protocol));
		return 0;
	}
	
	return ntohs(h.payload_length);
}


int getAcctIdFromHeader(char *src){
	header h;
	
	memcpy(&h, src, sizeof(h));
	
	return (ntohl(h.source_address) >> 8) % 100;
}

int getSourceFromHeader(char *src){
	header h;
	
	memcpy(&h, src, sizeof(h));
	
	return ntohl(h.source_address);
}



int sendMessageToGroup(char *msg){
	char buf[MAX_BUF_SIZE];
	int status, sendSock, bytesSent, buflen;
	struct addrinfo addrCriteria;
	struct addrinfo *multicastAddr;
	u_char mcTTL = (u_char) 1;
	
	if(!(make_header_str(buf, strlen(msg)))){
		my_error("make_header_str() failed");
	}
	
	strncpy(buf + 20, msg, strlen(msg));
	
	buflen = 20 + strlen(msg);
	buf[buflen] = '\0';
	//printf("from within sMTG(), message: %s\n", (buf + 20));
	
	
	memset(&addrCriteria, 0, sizeof(addrCriteria));
	addrCriteria.ai_family = AF_INET;
	addrCriteria.ai_socktype = SOCK_DGRAM;
	addrCriteria.ai_protocol = IPPROTO_UDP;
	addrCriteria.ai_flags |= AI_NUMERICHOST;
	
	
	status = getaddrinfo(ADDRESS, SERVICE, &addrCriteria, &multicastAddr);
	if(status != 0){
		my_error("getaddrinfo() failed");
	}
	
	sendSock = socket(multicastAddr->ai_family, multicastAddr->ai_socktype, multicastAddr->ai_protocol);
	//should be identical to socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if(sendSock < 0){
		my_error("socket() failed");
	}
	
	
	/*
		This is the section to be worked.  The rest works fine... I think.
		Just need to open up a socket for sending, and then send the message in buf (header and payload already properly formed).
	*/
	
	if(setsockopt(sendSock, IPPROTO_IP, IP_MULTICAST_TTL, &mcTTL, sizeof(mcTTL)) < 0){
		my_error("setsockopt(IP_MULTICAST_TTL) failed");
	}
	
	bytesSent = sendto(sendSock, buf, buflen, 0, multicastAddr->ai_addr, multicastAddr->ai_addrlen);
	if(bytesSent < 0){
		my_error("sendto() failed");
	} else if(bytesSent != buflen){
		my_error("sendto() didn't send right number of bytes");
	} 
	
	freeaddrinfo(multicastAddr);
	close(sendSock);
	return 1;
}


int appendNode(neighborNode **head, char *ip, int port, int netAddr, int accountID){
	neighborNode *curr;
	neighborNode *newNode = NULL;
	int done = 0;
	
	if(*head == NULL){
		my_error("head shouldn't be null");
	}
	
	for(curr = *head; curr->next == NULL; curr = curr->next){
		
		if((0 == strcmp(ip, curr->ip)) && (port == curr->port)){
			curr->netAddr = netAddr;
			curr->accountID = accountID;
			curr->isAlive = 1;
			curr->lastMsgTime = wallTime();
			done = 1;
		}
		
	}
	
	if(!done){
		newNode = malloc(sizeof(neighborNode));
		strcpy(newNode->ip, ip);
		newNode->port = port;
		newNode->netAddr = netAddr;
		newNode->accountID = accountID;
		newNode->isAlive = 1;
		newNode->lastMsgTime = wallTime();
		curr->next = newNode;
	}
	
}

void clearNeighborNode(neighborNode *head){
	if(head == NULL){
		return;
	}else{
		clearNeighborNode(head->next);
		free(head);
	}
}

void printNeighbors(neighborNode *head){
	neighborNode *curr;
	char isAlive;
	int seconds;
	
	for(curr = head; curr != NULL; curr = curr->next){
		
		printf("%s,%d,%d,%d,%a,%d\n", curr->ip, curr->port, curr->netAddr, curr->accountID, isAlive, 
		
	}
}

int main(int argc, char *argv[]){
	
	/*
	*PART 1:
	*Set up multicast, identical to project 4
	*/
	
	
	char recvmsg[MAX_BUF_SIZE];
	char sendmsg[MAX_BUF_SIZE] = "hello";
	char port[20] = "14327";
	struct addrinfo addrCriteria;
	struct addrinfo *multicastAddr;
	struct sockaddr fromAddr;
	int x, y, z, a, b, c, i, recvSock, sendSock, recvmsgLen, timeX, timeY, portNum;
	struct ip_mreq joinRequest;
	neighborNode *head;
	
	
	head = NULL;
	z = 0;
	a = 1;
	switch(argc){
		case 1:
			break;
		case 4:
			portNum = atoi(argv[3]);
		case 3:
			timeY = atoi(argv[2]);
		case 2:
			timeX = atoi(argv[1]);
			break;
		default:
			my_error("Parameter(s): [<TIME-X> <TIME-Y> <port>]");
			break;
	}
	
	//printf("message: %s\n", sendmsg);
	
	memset(&addrCriteria, 0, sizeof(addrCriteria));
	addrCriteria.ai_family = AF_INET;
	addrCriteria.ai_socktype = SOCK_DGRAM;
	addrCriteria.ai_protocol = IPPROTO_UDP;
	addrCriteria.ai_flags |= AI_NUMERICHOST;
	
	x = getaddrinfo(ADDRESS, SERVICE, &addrCriteria, &multicastAddr);
	if(x != 0){
		my_error("getaddrinfo() failed");
	}
	
	recvSock = socket(multicastAddr->ai_family, multicastAddr->ai_socktype, multicastAddr->ai_protocol);
	//should be identical to socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if(recvSock < 0){
		my_error("socket() failed");
	}
	
	if(setsockopt(recvSock, SOL_SOCKET, SO_REUSEADDR, &a, sizeof(a)) < 0){
		my_error("setsockopt(SOL_SOCKET->SO_REUSEADDR) failed");
	}
	
	if(bind(recvSock, multicastAddr->ai_addr, multicastAddr->ai_addrlen) < 0){
		
		z = 1;
		for(c = portNum; c < portNum + 10000; c++){
			sprintf(port, "%d", c);
			
			
			x = getaddrinfo(ADDRESS, port, &addrCriteria, &multicastAddr);
			if(x != 0){
				my_error("getaddrinfo() failed");
			}
			
			recvSock = socket(multicastAddr->ai_family, multicastAddr->ai_socktype, multicastAddr->ai_protocol);
			if(recvSock < 0){
				my_error("socket() failed");
			}
			b = bind(recvSock, multicastAddr->ai_addr, multicastAddr->ai_addrlen);
			if(b >= 0) break;
		}
		if(c == 20000){
			my_error("bind() failed");
		} else {
			printf("port: %d\n", c);
		}
	}
	/*
		Now, join the multicast group
	*/
	
	joinRequest.imr_multiaddr = ((struct sockaddr_in *) multicastAddr->ai_addr)->sin_addr;
	joinRequest.imr_interface.s_addr = 0;
	
	if(setsockopt(recvSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &joinRequest, sizeof(joinRequest)) < 0){
		my_error("setsockopt(IPPROTO_IP->IP_ADD_MEMBERSHIP) failed");
	}
	
	
	
	/*
	*PART 2:
	*Test sending message to group and getting IP
	*/
	
	sendMessageToGroup(sendmsg);
	
	/*
	*PART 3:
	*Main event loop
	*/
		//Calculate timeout
		//Option 1: Timeout
		//Option 2: Error
		//Option 3: Read message or command
	
	/*
	*PART 4:
	*Close socket, free memory
	*/
	
	
	freeaddrinfo(multicastAddr);
	close(recvSock);
	clearNeighborNode(head);
	return 0;
}








